-
Notifications
You must be signed in to change notification settings - Fork 0
feat: #10 simulated user identity: User type and the X-User-Id contract #40
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good overall. One small wording correction: deriving displayName server-side prevents the client from choosing an arbitrary display name, but it does not prevent impersonation because a client can still send another UUID in X-User-Id. That is acceptable for simulated users without authentication, but the documentation should state this precisely.
|
You are right and the wording was overclaiming. Fixed. What deriving What it does not buy: anything about identity. The three places that said a review "cannot be attributed to someone else" now say the opposite, in
Which is the honest version, and as you say, fine for simulated users. Worth having the precise version written down, because "why is this not authentication" is exactly the question that gets asked when this comes up in a review or a viva. |
Spec work for the simulated user. No runtime yet; this is the contract
the client and the server will both implement.
schema.graphql
Adds `type User { id, displayName }` and `Review.user: User!`. Reviews
rendered without an author were a gap: the brief asks that
user-generated data is presented, and an unattributed review does not
present it. Checked that no root field and no user-scoped field takes a
userId argument, which is the point of routing identity through the
context instead.
AGENTS.md rule 5
Was wrong: "sent with every mutation". myReview, isInCrate and myCrate
are queries, so they need the identity too. Rewritten to cover the
header, the per-request context, anonymous fallback, and why displayName
is derived server-side rather than sent by the client.
docs/architecture.md decision 5
Even had the decision right ("sends it with each request") but not the
transport. Adds why a header and not a GraphQL variable, cookie or
Authorization header, the missing/invalid behaviour table, and the
consequence that displayName is not client-supplied.
docs/data-model.md
Adds the user to the user-generated data section and points at
decision 5.
SDL validated with buildSchema: Review and User parse, and every
user-scoped field takes zero arguments.
Co-Authored-By: opencode <noreply@opencode.ai>
robertky pointed out the wording overclaimed. Deriving displayName server-side stops the client choosing an arbitrary name string. It does not stop impersonation, because X-User-Id is client-supplied and anyone can send someone else's UUID and write as them. That is fine for simulated users, which is what the brief allows, but the docs should not say otherwise. Three places claimed a review "cannot be attributed to someone else", which is the wrong guarantee. Now stated as: the UUID separates visitors, it does not authenticate them. Deriving the name stops the name being chosen by the client, not the identity being claimed. Reported by robertky. Co-Authored-By: opencode <noreply@opencode.ai>
ff2db5e to
56813d1
Compare
|
Rebased onto To answer the question properly: GitHub said There was no file overlap, so the rebase was clean. 4 files, unchanged: Gates against the new base, which now includes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good now, thanks for the updates!
|
Please remember to delete the branch after merging, i will do it for you this time :) |
What this does
Spec work for #10. No runtime yet. This is the contract that the client and the server will both implement in M2.
What was done
schema.graphqlReviews rendered without an author was a real gap. The brief asks that user-generated data is presented, and an unattributed review does not present it.
displayNameis deliberately not inUpsertReviewInput, so a review cannot be attributed to a name the sender did not earn.AGENTS.mdrule 5 was wrong. It said the UUID is "sent with every mutation".myReview,isInCrateandmyCrateare queries and need the identity just as much. Rewritten to cover the header, the per-request context, the anonymous fallback, and whydisplayNameis derived server-side.docs/architecture.mddecision 5. Even had the decision right ("sends it with each request") but not the transport. This adds:X-User-Idheader and not a GraphQL variable, a cookie, orAuthorizationdisplayNameis derived from the UUID rather than supplieddocs/data-model.mdgains the user in the user-generated data section.Why a header and not a GraphQL variable
myReview,isInCrateandmyCratecurrently take zero arguments, which is deliberate. AuserIdargument on each would mean threading$userIdthrough every operation and would make identity look like data when it is really transport. The header is set once by an Apollo link and read once into the context.It is also not
Authorization, because nothing here is authenticated. Anyone can send any UUID. Calling itAuthorizationwould be misleading when we are asked to defend this in a viva.Missing or invalid
myReviewisInCratemyCratenullfalsenullfalsenullfalseReads never fail because of identity state, so a bad header cannot take the search page down. Writes require it and say so rather than attributing the change to nobody.
What was tested
Validated the SDL with
buildSchema:Also
pnpm format:check,pnpm lint,pnpm typecheckandpnpm test(3 tests) all pass.Closes
Closes the spec half of #10. The implementation (Apollo link, context, lazy user row) is the remaining half and needs
server/from #6.Related
@evenkkl decision 5 in
docs/architecture.mdis yours and I extended it rather than replacing it. Please check the transport section reads the way you intended.